/* Font.h
Copyright (c) 2008 Michael Zahniser
Please note the license terms (MIT license) at the end of this document.

Header file for the Font class, which represents a bitmap font.
*/

#ifndef __MZ_FONT_H__
#define __MZ_FONT_H__

#include "GlyphSet.h"

#include "Surface.h"

#include <string>
#include <vector>

using std::string;
using std::vector;



class Font : public GlyphSet {
public:
	// Load an image file and use it as the glyphs for the font.
	// The file should have ASCII characters 33 to 126 ('!' to '~') in order, with
	// at least one empty column (completely transparent) after each character.
	// After the ~, include a glyph to be used for undefined characters.
	void Load(const string &path);
	void Load(const string &data, int width, int height);
	
	// Draw the given string, with its top left corner at the given (x, y) location.
	// If isContinuing, the first character will be kerned to the last character of
	// the most recently processed (by Draw() or Width()) string.
template <class Text>
	int Draw(const Text &text, Surface &surface, int x, int y, bool isContinuing = false) const;
template <typename It>
	int Draw(It it, It end, Surface &surface, int x, int y, bool isContinuing = false) const;

	// Determine the width of the given string.
	// If isContinuing, the string is kerned to the most recently processed string.
template <class Text>
	int Width(const Text &text, bool isContinuing = false) const;
template <typename It>
	int Width(It it, It end, bool isContinuing = false) const;
	
	// Determine where to wrap the given string to fit it in the given width.
template <typename It>
	It Wrap(It it, It end, int &maxWidth) const;
	// Determine where in the given string the given x position is.
template <typename It>
	It Map(It it, It end, int x) const;
	
	
private:
	void Initialize();
	
	// Convert a char to its index in the glyph image.
	int CharIndex(char a) const;
	// Get the minimum distance between the characters with the given two indices.
	// (Do _not_ give this function raw character values.)
	int Kerning(int aIndex, int bIndex) const;

	// Process the given text, applying the given functor to each character.
	// The functor must have an application operator of the form
	// void operator()(int sourceX, int width, int destinationX).
template <typename It, class Functor>
	int Process(It it, It end, int x, bool isContinuing, Functor &fun) const;
	
	
private:
	// This class represents one scanline of a single character, and stores the
	// alpha values and positions of its first and last pixels, in order to do
	// sub-pixel kerning to another character.
	class CharScanline {
	public:
		CharScanline(const Surface &surface, int row, int minX, int maxX);
		
		// Calculate the distance between the last pixel of this scanline and the
		// first pixel of the next, using alpha values as fractional widths.
		int DistanceTo(const CharScanline &line) const;
		
	private:
		unsigned char width;
		unsigned char firstPixel;
		unsigned char lastPixel;
		unsigned char firstAlpha;
		unsigned char lastAlpha;
	};

	// Functor for drawing text.
	class DrawText {
	public:
		DrawText(const Surface &source, const Surface &destination, int y);

		void operator()(int sourceX, int width, int destinationX);

	private:
		Surface source;
		Surface destination;
		int y;
		int height;
	};

	// Functor for measuring text, without drawing it.
	// This is a null functor, because measurement can be done entirely by
	// examining the end value of the x position.
	class MeasureText {
	public:
		void operator()(int, int, int) {}
	};
	
	
private:
	// Skip characters 0..31 and characters 127...255.
	// That leaves 128 - 32 - 1 = 95 characters, plus one more
	// glyph for any characters outside that range.
	static const int GLYPHS = 128 - 32;
	// The kerning algorithm finds the point where two characters
	// come closest to touching, and makes sure that they are this
	// distance apart at that point.
	static const int KERNING = 2;
	// This value will be adjusted based on the character height.
	int SPACE;
	// This is the width of a tab.
	int TAB;
	// The width of a space is the height divided by this value.
	static const int SPACE_FRACTION = 3;
	

private:
	// The x offsets of the start and end of each glyph in the image.
	int offset[GLYPHS * 2];
	
	// Cache the kerning distance for each pair of glyphs.
	// kerning[a + b * GLYPHS] = Kerning(a, b).
	int kerning[GLYPHS * GLYPHS];
	
	// Data on each scanline of each glyph.
	vector<CharScanline> scanline;
	
	// The index of the most recently drawn glyph. This is used to
	// allow accurate kerning across multiple calls to Draw() or Width().
	mutable int previous;
};



// Templated function definitions:
	
// Draw (text):
template <class Text>
int Font::Draw(const Text &text, Surface &surface, int x, int y, bool isContinuing) const
{
	return Draw(text.begin(), text.end(), surface, x, y, isContinuing);
}


	
// Draw (begin, end):
template <typename It>
int Font::Draw(It it, It end, Surface &surface, int x, int y, bool isContinuing) const
{
	DrawText drawText(*this, surface, y);
	return Process(it, end, x, isContinuing, drawText);
}



// Width (text):
template <class Text>
int Font::Width(const Text &text, bool isContinuing) const
{
	return Width(text.begin(), text.end(), isContinuing);
}



// Width (begin, end):
template <typename It>
int Font::Width(It it, It end, bool isContinuing) const
{
	MeasureText measureText;
	return Process(it, end, 0, isContinuing, measureText);
}
	


// Determine where to wrap the given string to fit it in the given width.
template <typename It>
It Font::Wrap(It it, It end, int &maxWidth) const
{
	int width = Width(it, it);
	
	It cut = it;
	while(cut != end)
	{
		// Find the next white space.
		It newCut = cut;
		while(newCut != end && *newCut > ' ')
			++newCut;
		
		// Can we add this word without exceeding the width?
		int newWidth = width + Width(cut, newCut, true);
		if(newWidth > maxWidth)
			break;
		
		// Include the white space after this word.
		// It doesn't matter if white space extends beyond the max width.
		cut = newCut;
		while(cut != end && *cut <= ' ')
			++cut;
		
		// Add in the width of the white space after the word.
		width = newWidth + Width(newCut, cut, true);
	}
	
	// Special case: there was no "good" place to cut.
	// Cut anywhere in the text, at the maximum width.
	if(cut == it)
		cut = Map(it, end, maxWidth);
	else
		maxWidth = width;
	
	return cut;
}



// Determine where in the given string the given x position is.
template <typename It>
It Font::Map(It it, It end, int x) const
{
	int width = Width(it, it);
	
	for( ; it != end; ++it)
	{
		width += Width(it, it + 1, true);
		if(width > x)
			break;
	}
	
	return it;
}



// Process:
// Process the given text, applying the given functor to each character.
// The functor must have an application operator of the form
// void operator()(int sourceX, int width, int destinationX).
template <typename It, class Functor>
int Font::Process(It it, It end, int x, bool isContinuing, Functor &fun) const
{
	// We can do nothing if no glyph image is loaded.
	if(Surface::operator bool())
	{
		// If we are not continuing from a previous string, start this string
		// as if the previous character was a space.
		if(!isContinuing)
			previous = 0;

		for( ; it != end; ++it)
		{
			if(*it == '\t')
				x += TAB;
			
			// Get the index of the current character. If it is a space, just
			// update the x position; there is no need to draw anything.
			int current = CharIndex(*it);
			if(!current)
				x += SPACE;
			else
			{
				// Adjust kerning. All spacings are cached already.
				x += kerning[previous + current * GLYPHS];

				// Get the position and size of this character's glyph.
				int sourceX = offset[2 * current];
				int width = offset[2 * current + 1] - sourceX;

				// Apply the function to this glyph.
				fun(sourceX, width, x);

				// Advance the draw position by the width of the glyph.
				x += width;
				
				// If there is a space in the string, the kerning will be adjusted
				// based on the last character _before_ the space.
				previous = current;
			}
		}
	}
	
	// Return the final x position.
	return x;
}



#endif

/* Copyright (c) 2008 Michael Zahniser

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE. */
